package com.yuno.mock.service;

import com.yuno.mock.config.MinioProperties;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * MinIO文件服务
 *
 * @author xrm
 * @since 2025-11-06
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MinioService {

    private final MinioClient minioClient;
    private final MinioProperties minioProperties;

    // 文件上传进度跟踪
    private final Map<String, Double> uploadProgressMap = new ConcurrentHashMap<>();
    // 文件下载进度跟踪
    private final Map<String, Double> downloadProgressMap = new ConcurrentHashMap<>();
    // 文件操作任务状态
    private final Map<String, String> taskStatusMap = new ConcurrentHashMap<>();

    // 异步执行器
    private final Executor asyncExecutor = Executors.newCachedThreadPool();

    /**
     * 初始化存储桶
     */
    public void initBucket() {
        try {
            boolean bucketExists = minioClient.bucketExists(
                    BucketExistsArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .build()
            );

            if (!bucketExists) {
                minioClient.makeBucket(
                        MakeBucketArgs.builder()
                                .bucket(minioProperties.getBucketName())
                                .region(minioProperties.getRegion())
                                .build()
                );
                log.info("创建存储桶: {}", minioProperties.getBucketName());
            } else {
                log.info("存储桶已存在: {}", minioProperties.getBucketName());
            }
        } catch (Exception e) {
            log.error("初始化存储桶失败: {}", e.getMessage(), e);
            throw new RuntimeException("初始化存储桶失败", e);
        }
    }

    /**
     * 上传文件
     *
     * @param file 要上传的文件
     * @return 文件访问URL
     */
    public String uploadFile(MultipartFile file) {
        String fileName = generateUniqueFileName(file.getOriginalFilename());
        return uploadFile(file, fileName);
    }

    /**
     * 上传文件到指定文件名
     *
     * @param file 要上传的文件
     * @param fileName 目标文件名
     * @return 文件访问URL
     */
    public String uploadFile(MultipartFile file, String fileName) {
        try {
            log.info("开始上传文件到MinIO: {}", fileName);

            // 上传文件
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .object(fileName)
                            .stream(file.getInputStream(), file.getSize(), -1)
                            .contentType(file.getContentType())
                            .build()
            );

            log.info("文件上传成功: {}", fileName);
            return getFileUrl(fileName);

        } catch (Exception e) {
            log.error("文件上传失败: {}", e.getMessage(), e);
            throw new RuntimeException("文件上传失败: " + e.getMessage(), e);
        }
    }

    /**
     * 异步上传文件
     *
     * @param file 要上传的文件
     * @return CompletableFuture包含文件URL
     */
    public CompletableFuture<String> uploadFileAsync(MultipartFile file) {
        String fileName = generateUniqueFileName(file.getOriginalFilename());
        String taskId = UUID.randomUUID().toString();

        return CompletableFuture.supplyAsync(() -> {
            taskStatusMap.put(taskId, "UPLOADING");
            uploadProgressMap.put(taskId, 0.0);

            try {
                // 模拟进度更新
                for (int i = 0; i <= 100; i += 10) {
                    Thread.sleep(100);
                    uploadProgressMap.put(taskId, (double) i / 100);
                }

                String url = uploadFile(file, fileName);
                taskStatusMap.put(taskId, "COMPLETED");
                uploadProgressMap.put(taskId, 1.0);
                return url;

            } catch (Exception e) {
                taskStatusMap.put(taskId, "FAILED");
                log.error("异步文件上传失败: {}", e.getMessage(), e);
                throw new RuntimeException("文件上传失败", e);
            }
        }, asyncExecutor);
    }

    /**
     * 下载文件
     *
     * @param fileName 文件名
     * @return 文件输入流
     */
    public InputStream downloadFile(String fileName) {
        try {
            log.info("开始下载文件: {}", fileName);

            return minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .object(fileName)
                            .build()
            );

        } catch (Exception e) {
            log.error("文件下载失败: {}", e.getMessage(), e);
            throw new RuntimeException("文件下载失败: " + e.getMessage(), e);
        }
    }

    /**
     * 异步下载文件
     *
     * @param fileName 文件名
     * @return CompletableFuture包含文件字节数组
     */
    public CompletableFuture<byte[]> downloadFileAsync(String fileName) {
        String taskId = UUID.randomUUID().toString();

        return CompletableFuture.supplyAsync(() -> {
            taskStatusMap.put(taskId, "DOWNLOADING");
            downloadProgressMap.put(taskId, 0.0);

            try (InputStream inputStream = downloadFile(fileName)) {
                // 模拟进度更新
                for (int i = 0; i <= 100; i += 10) {
                    Thread.sleep(50);
                    downloadProgressMap.put(taskId, (double) i / 100);
                }

                byte[] bytes = inputStream.readAllBytes();
                taskStatusMap.put(taskId, "COMPLETED");
                downloadProgressMap.put(taskId, 1.0);
                return bytes;

            } catch (Exception e) {
                taskStatusMap.put(taskId, "FAILED");
                log.error("异步文件下载失败: {}", e.getMessage(), e);
                throw new RuntimeException("文件下载失败", e);
            }
        }, asyncExecutor);
    }

    /**
     * 删除文件
     *
     * @param fileName 文件名
     */
    public void deleteFile(String fileName) {
        try {
            log.info("开始删除文件: {}", fileName);

            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .object(fileName)
                            .build()
            );

            log.info("文件删除成功: {}", fileName);

        } catch (Exception e) {
            log.error("文件删除失败: {}", e.getMessage(), e);
            throw new RuntimeException("文件删除失败: " + e.getMessage(), e);
        }
    }

    /**
     * 列出所有文件
     *
     * @return 文件列表
     */
    public List<Map<String, Object>> listFiles() {
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .build()
            );

            List<Map<String, Object>> fileList = new ArrayList<>();
            for (Result<Item> result : results) {
                Item item = result.get();
                Map<String, Object> fileInfo = new HashMap<>();
                fileInfo.put("fileName", item.objectName());
                fileInfo.put("size", item.size());
                fileInfo.put("lastModified", item.lastModified());
                fileInfo.put("etag", item.etag());
                fileInfo.put("url", getFileUrl(item.objectName()));
                fileList.add(fileInfo);
            }
            log.info("获取文件列表，存储桶名称: {}，获取文件列表成功: {}个文件", minioProperties.getBucketName(), fileList.size());
            return fileList;

        } catch (Exception e) {
            log.error("获取文件列表失败: {}", e.getMessage(), e);
            throw new RuntimeException("获取文件列表失败", e);
        }
    }

    /**
     * 检查文件是否存在
     *
     * @param fileName 文件名
     * @return 是否存在
     */
    public boolean fileExists(String fileName) {
        try {
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(minioProperties.getBucketName())
                            .object(fileName)
                            .build()
            );
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取文件访问URL
     *
     * @param fileName 文件名
     * @return 文件URL
     */
    public String getFileUrl(String fileName) {
        try {
            // 生成预签名URL，有效期7天
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(minioProperties.getBucketName())
                            .object(fileName)
                            .expiry(60 * 60 * 24 * 7) // 7天
                            .build()
            );
        } catch (Exception e) {
            log.error("获取文件URL失败: {}", e.getMessage(), e);
            return minioProperties.getEndpoint() + "/" + minioProperties.getBucketName() + "/" + fileName;
        }
    }

    /**
     * 获取上传进度
     *
     * @param taskId 任务ID
     * @return 进度百分比
     */
    public Double getUploadProgress(String taskId) {
        return uploadProgressMap.getOrDefault(taskId, 0.0);
    }

    /**
     * 获取下载进度
     *
     * @param taskId 任务ID
     * @return 进度百分比
     */
    public Double getDownloadProgress(String taskId) {
        return downloadProgressMap.getOrDefault(taskId, 0.0);
    }

    /**
     * 获取任务状态
     *
     * @param taskId 任务ID
     * @return 任务状态
     */
    public String getTaskStatus(String taskId) {
        return taskStatusMap.get(taskId);
    }

    /**
     * 生成唯一文件名
     *
     * @param originalFileName 原始文件名
     * @return 唯一文件名
     */
    private String generateUniqueFileName(String originalFileName) {
        String extension = "";
        if (originalFileName != null && originalFileName.contains(".")) {
            extension = originalFileName.substring(originalFileName.lastIndexOf("."));
        }
        return UUID.randomUUID().toString() + extension;
    }
}